/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.ArrayList;
import java.util.Calendar;

class GlobalConfig {
    public final static int TIME_STAMP_SECONDS_CONST = 1167585071;
    public final static long TIME_STAMP_MILLISECONDS_CONST = 1167585071000L;
    public final static int TIME_ADD_CONST = 84266956;
    public final static int TIME_ZONE_OFFSET_CONST = -480;
    public final static int YEAR_CONST = 2007;
    public final static int TIME_CONVERT_CONST = 60;
    public final static int TIME_HOUR_12_CONST = 12;
    public final static int YEAR_STARD_CONST = 100;
    public final static int YEAR_LEEP_CONST = 400;
    public final static int NUM_1_CONST = 1;
    public final static int NUM_2_CONST = 2;
    public final static int NUM_4_CONST = 4;
    public final static int NUM_3_CONST = 3;
    public final static int NUM_7_CONST = 7;
    public final static int NUM_11_CONST = 11;
    public final static int NUM_21_CONST = 21;
    public final static int NUM_22_CONST = 22;
    public final static int NUM_23_CONST = 23;
    public final static int NUM_28_CONST = 28;
    public final static int NUM_29_CONST = 29;
    public final static int NUM_30_CONST = 30;
    public final static int NUM_31_CONST = 31;
    public final static int NUM_500_CONST = 500;
    public final static int NUM_TIME_CONST = 1000;
    public final static int NUM_LOOPCOUNT_CONST = 80;
}

class MyDate {
    public int timeIntervalSince1970() {
        return GlobalConfig.TIME_STAMP_SECONDS_CONST;
    }

    public int timeStamp() {
        return GlobalConfig.TIME_STAMP_SECONDS_CONST;
    }

    public long milliStamp() {
        return GlobalConfig.TIME_STAMP_MILLISECONDS_CONST;
    }

    public int getDate() {
        // Returns a day of the month
        return GlobalConfig.NUM_1_CONST;
    }

    public int getTimezoneOffset() {
        // Returns the time difference between Greenwich Mean Time and local time:
        return GlobalConfig.TIME_ZONE_OFFSET_CONST;
    }

    public int getYear() {
        return GlobalConfig.YEAR_CONST;
    }

    public int getFullYear() {
        // return a year
        return GlobalConfig.YEAR_CONST;
    }

    public int getMonth() {
        // return a month
        return GlobalConfig.NUM_1_CONST;
    }

    public int getHours() {
        // Returns the hour field of the time based on the specified time:
        return GlobalConfig.NUM_1_CONST;
    }

    public int getMinutes() {
        // Returns the minute field of the time based on the specified time:
        return GlobalConfig.NUM_11_CONST;
    }

    public int getSeconds() {
        // Returns the second field of the time based on the specified time:
        return GlobalConfig.NUM_11_CONST;
    }

    public int getDay() {
        // Returns the number of a day of the week.
        return GlobalConfig.NUM_1_CONST;
    }

    public long getTime() {
        // Returns the number of milliseconds between January 1, 1970:
        int timeInterval = timeIntervalSince1970();
        return (long) timeInterval * GlobalConfig.NUM_TIME_CONST;
    }

    public void setTime(long millisecond) {
        // Method to set the Date object in milliseconds.
    }
}

class DateClass extends MyDate {
    public static int[] daysInMonth = {
            GlobalConfig.NUM_31_CONST,
            GlobalConfig.NUM_28_CONST,
            GlobalConfig.NUM_31_CONST,
            GlobalConfig.NUM_30_CONST,
            GlobalConfig.NUM_31_CONST,
            GlobalConfig.NUM_30_CONST,
            GlobalConfig.NUM_31_CONST,
            GlobalConfig.NUM_31_CONST,
            GlobalConfig.NUM_30_CONST,
            GlobalConfig.NUM_31_CONST,
            GlobalConfig.NUM_30_CONST,
            GlobalConfig.NUM_31_CONST
    };
    public static String[] monthNames = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};
    public static String[] dayNames = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

    DateClass(String str) {
         super();
    }

    public String dateFormat(String format) {
        return createNewFormat(format);
    }

    public String createNewFormat(String format) {
        String result = "";
        boolean special = false;
        ArrayList<Character> arr = this.stringToArray(format);
        for (char ch : arr) {
            if (!special && ch == '\\') {
                special = true;
            } else if (special) {
                special = false;
                result += ch;
            } else {
                String value = this.getFormatCode(ch);
                result += value;
            }
        }
        return result;
    }

    public String getFormatCode(char character) {
        switch (character) {
            case 'd':
                return leftPad(getDate(), GlobalConfig.NUM_2_CONST, "0");
            case 'D':
                return substring(dayNames[getDay()], 0, GlobalConfig.NUM_3_CONST);
            case 'j':
                return String.format("%d",getDate());
            case 'l':
                return dayNames[getDay()];
            case 'S':
                return getSuffix();
            case 'w':
                return String.format("%d",getDay());
            case 'z':
                return String.format("%d",getDayOfYear());
            case 'W':
                return getWeekOfYear();
            case 'F':
                return monthNames[getMonth() - 1];
            case 'm':
                return leftPad(getMonth(), GlobalConfig.NUM_2_CONST, "0");
            case 'M':
                return substring(monthNames[getMonth()], 0, GlobalConfig.NUM_3_CONST);
            case 'n':
                return String.format("%d",getMonth() + 1);
            case 't':
                return String.format("%d",getDaysInMonth());
            case 'L':
                return isLeapYear() ? "1" : "0";
            case 'Y':
                return String.format("%d",getFullYear());
            case 'y':
                return substring(Integer.toString(getFullYear()), GlobalConfig.NUM_2_CONST, GlobalConfig.NUM_4_CONST);
            case 'a':
                return getHours() < GlobalConfig.TIME_HOUR_12_CONST ? "am" : "pm";
            case 'A':
                return getHours() < GlobalConfig.TIME_HOUR_12_CONST ? "AM" : "PM";
            case 'g':
                return String.format("%d",(getHours() % GlobalConfig.TIME_HOUR_12_CONST) != 0 ? getHours() % GlobalConfig.TIME_HOUR_12_CONST : GlobalConfig.TIME_HOUR_12_CONST);
            case 'G':
                return String.format("%d",getHours());
            case 'h':
                return leftPad(((getHours() % GlobalConfig.TIME_HOUR_12_CONST) != 0 ? getHours() % GlobalConfig.TIME_HOUR_12_CONST : GlobalConfig.TIME_HOUR_12_CONST), GlobalConfig.NUM_2_CONST, "0");
            case 'H':
                return leftPad(getHours(), GlobalConfig.NUM_2_CONST, "0");
            case 'i':
                return leftPad(getMinutes(), GlobalConfig.NUM_2_CONST, "0");
            case 's':
                return leftPad(getSeconds(), GlobalConfig.NUM_2_CONST, "0");
            case 'O':
                return getGMTOffset();
            case 'Z':
                return String.format("%d",getTimezoneOffset() * -GlobalConfig.TIME_CONVERT_CONST);
            default:
                return String.format("%c",character);
        }
    }

    public String getGMTOffset() {
        return (
                getTimezoneOffset() > 0 ? "-" : "+")
                + leftPad(Math.floorDiv(getTimezoneOffset(), GlobalConfig.TIME_CONVERT_CONST), GlobalConfig.NUM_2_CONST, "0")
                + leftPad(getTimezoneOffset() % GlobalConfig.TIME_CONVERT_CONST, GlobalConfig.NUM_2_CONST, "0"
        );
    }

    public int getDayOfYear() {
        int num = 0;
        daysInMonth[1] = isLeapYear() ? GlobalConfig.NUM_29_CONST : GlobalConfig.NUM_28_CONST;
        for (int i = 0; i < getMonth(); i++) {
            num += daysInMonth[i];
        }
        return num + getDate() - 1;
    }

    public String getWeekOfYear() {
        // Skip to Thursday of this week
        int now = getDayOfYear() + (GlobalConfig.NUM_4_CONST - getDay());
        // Find the first Thursday of the year
        int jan1 = getFullYear();
        int then = (GlobalConfig.NUM_7_CONST - Calendar.JANUARY % GlobalConfig.NUM_7_CONST + 4);
        return leftPad(((now - then) / GlobalConfig.NUM_7_CONST) + 1, GlobalConfig.NUM_2_CONST, "0");
    }

    public boolean isLeapYear() {
        int year = getFullYear();
        return ((year & GlobalConfig.NUM_3_CONST) == 0 && (((year % GlobalConfig.YEAR_STARD_CONST) != 0) || (year % GlobalConfig.YEAR_LEEP_CONST == 0 && (year != 0))));
    }

    public int getDaysInMonth() {
        daysInMonth[1] = isLeapYear() ? GlobalConfig.NUM_29_CONST : GlobalConfig.NUM_28_CONST;
        return daysInMonth[getMonth()];
    }

    public String getSuffix() {
        switch (getDate()) {
            case 1:
            case GlobalConfig.NUM_21_CONST:
            case GlobalConfig.NUM_31_CONST:
                return "st";
            case GlobalConfig.NUM_2_CONST:
            case GlobalConfig.NUM_22_CONST:
                return "nd";
            case GlobalConfig.NUM_3_CONST:
            case GlobalConfig.NUM_23_CONST:
                return "rd";
            default:
                return "th";
        }
    }

    public String leftPad(int val, int size, String ch) {
        String str = ch;
        String result = String.valueOf(val);
        if (str == null) {
            str = " ";
        }
        while (result.length() < size) {
            result = str + result;
        }
        return result;
    }

    public ArrayList<Character> stringToArray(String str) {
        ArrayList<Character> arr = new ArrayList<>();
        for (int i = 0; i < str.length(); i++) {
            arr.add(str.charAt(i));
        }
        return arr;
    }

    public String substring(String str, int startOffset, int endOffset) {
        return str.substring(startOffset, endOffset);
    }
}

class Benchmark {
    public static void main(String[] args) {
        Benchmark benchmark = new Benchmark();
        benchmark.runIterationTime();
    }

    public void run() {
        DateClass date = new DateClass("1/1/2007 01:11:11");
        for (int i = 0; i < GlobalConfig.NUM_500_CONST; i++) {
            String shortFormat = date.dateFormat("Y-m-d");
            String longFormat = date.dateFormat("l, F d, Y g:i:s A");
//            System.out.println("year is : " + date.dateFormat("Y"));
//            System.out.println("month is : " + date.dateFormat("m"));
//            System.out.println("day is : " + date.dateFormat("d"));
//            System.out.println("Y-m-d: " + shortFormat);
//            System.out.println("l, F d, Y g:i:s A: " + longFormat);
            date.setTime(date.getTime() + GlobalConfig.TIME_ADD_CONST);
        }
    }

    public void runIterationTime() {
        double start = (double) System.nanoTime() / 1_000_000;
        for (int i = 0; i < GlobalConfig.NUM_LOOPCOUNT_CONST; i++) {
            run();
        }
        double end = (double) System.nanoTime() / 1_000_000;
        double duration = (end - start);
        System.out.println("date-format-xparb: ms = " + duration);
    }
}
